/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.alertnetusa.validator;

import com.incident.cap.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.StringTokenizer;

/** 
 * An EasCapArea object within a CAP 1.1 message EasCapInfo block.<p>
 * <i>Copyright (c) 2008-2009 Thai Van<p>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * @see EasCapInfo
 * @author Thai Van, Prafash Kumar. This class is based on Art Botterell, David R Robinson work on com.incident.cap.Alert
 * @version 1 Oct 2008
 */

public class EasCapArea {

    /** the EasCapInfo block of which this EasCapArea belong to */
    protected EasCapInfo parentInfo = null;
    /** String buffer for use through out this class */
    private StringBuffer buf;
    /** area description*/
    protected String areaDesc = null;                   // EasCapArea Description
    /** ArrayList of polygons within this area */
    protected ArrayList polygons = new ArrayList();     // Polygons
    /** area ListIterator of polygons for easy traversal */
    protected Iterator polygonIterator = polygons.iterator();
    /** ArrayList of circles within this area */
    protected ArrayList circles = new ArrayList();        // Circles
    /** area ListIterator of circles for easy traversal */
    protected Iterator circleIterator = circles.iterator();
    /** ArrayList of geocodes within this area */
    protected ArrayList geocodes = new ArrayList();       // Geocodes
    /** area ListIterator of geocodes for easy traversal */
    protected ListIterator geocodesListIterator = geocodes.listIterator();
    /** area altitude */
    protected String altitude = null;                     // Altitude
    /** area ceiling */
    protected String ceiling = null;                      // Ceiling

    /** Basic constructor */
    public EasCapArea() {
    }

    /** Constructor that sets backreference to parent EasCapInfo object
     * @param info the parent EasCapInfo object
     */
    public EasCapArea(EasCapInfo info) {
        setParentInfo(info);
    }
    // Parent object backreference (optional)
    /** Return the parent EasCapInfo object
     * @return the parent EasCapInfo object
     */
    public EasCapInfo getParentInfo() {
        return parentInfo;
    }

    /** Set backreference to parent EasCapInfo object
     * @param info the parent EasCapInfo object
     */
    public void setParentInfo(EasCapInfo info) {
        parentInfo = info;
    }

    /** Gets area description  */
    public String getAreaDesc() {
        return areaDesc;
    }

    /** Sets area description  */
    public void setAreaDesc(String value) {
        areaDesc = value;
    }

    /** Adds a Polygon entry */
    public void addPolygon(String value) {
        polygons.add(value);
    }

    /** Reset Polygon index to first instance */
    public void resetPolygons() {
        polygonIterator = polygons.iterator();
    }

    /** Retrieves next Polygon entry */
    public String getNextPolygon() {
        return (String) polygonIterator.next();
    }

    /** Removes current Polygon entry */
    public void removePolygon() {
        polygonIterator.remove();
    }

    /** Says if there's another Polygon entry */
    public boolean hasNextPolygon() {
        return polygonIterator.hasNext();
    }

    /** Returns number of Polygon entries */
    public int countPolygons() {
        return polygons.size();
    }

    /** Adds a Circle entry */
    public void addCircle(String value) {
        circles.add(value);
    }

    /** Reset Circle index to first instance */
    public void resetCircles() {
        circleIterator = circles.iterator();
    }

    /** Retrieves next Circle entry */
    public String getNextCircle() {
        return (String) circleIterator.next();
    }

    /** Removes current Circle entry */
    public void removeCircle() {
        circleIterator.remove();
    }

    /** Says if there's another Circle entry */
    public boolean hasNextCircle() {
        return circleIterator.hasNext();
    }

    /** Returns number of Circle entries */
    public int countCircles() {
        return circles.size();
    }

    /** Append a supplied Geocode to this EasCapInfo  */
    public void addGeocode(String valueKey, String value) {
        while (hasNextGeocode()) {
            try {
                getNextGeocodeValuePair();
            } catch (Exception e) {
            }
        } // move to end of list
        geocodesListIterator.add(new ValuePair(valueKey, value));
    }

    /** Append a supplied Geocode in CAP 1.0 format */
    public void addGeocode(String pair) {
        while (hasNextGeocode()) {
            try {
                getNextGeocodeValuePair();
            } catch (Exception e) {
            }
        } // move to end of list
        try {
            StringTokenizer st = new StringTokenizer(pair, "=");
            geocodesListIterator.add(new ValuePair(st.nextToken(), st.nextToken()));
        } catch (Exception e) {
        }
    }

    /** Reset Geocode access to first instance */
    public void resetGeocodes() {
        geocodesListIterator = geocodes.listIterator();
    }

    /** Get next Geocode entry as CAP 1.0 string*/
    public String getNextGeocode() {
        return ((ValuePair) geocodesListIterator.next()).toCAP10();
    }

    /** Get next Geocode as CAP 1.1-style ValuePair object */
    public ValuePair getNextGeocodeValuePair() {
        if (hasNextGeocode()) {
            return (ValuePair) geocodesListIterator.next();
        } else {
            return null;
        }
    }

    /** Remove current Geocode entry */
    public void removeGeocode() {
        geocodesListIterator.remove();
    }

    /** Test if there's another Geocode entry */
    public boolean hasNextGeocode() {
        return geocodesListIterator.hasNext();
    }

    /** Return number of Geocode entries */
    public int countGeocodes() {
        return geocodes.size();
    }    

    /** Gets altitude value (feet AMSL) */
    public String getAltitude() {
        return altitude;
    }

    /** Sets altitude value (feet AMSL)  */
    public void setAltitude(String value) {
        altitude = value;
    }   

    /** Gets ceiling value (feet AMSL) */
    public String getCeiling() {
        return ceiling;
    }

    /** Sets ceiling value  (feet AMSL) */
    public void setCeiling(String value) {
        ceiling = value;
    }

    /** Get bounding box (latMax, lonMin latMin,lonMax) for all polygons and circles in this EasCapArea */
    public String getBounds() {
        String bounds = "";
        double latMax = -90.0;
        double latMin = 90.0;
        double lonMax = -180.0;
        double lonMin = 180.0;
        if (countCircles() > 0) {
            resetCircles();
            while (hasNextCircle()) {
                try {
                    StringTokenizer st = new StringTokenizer(getNextCircle());
                    StringTokenizer st2 = new StringTokenizer(st.nextToken(), ",");
                    double lat = Double.parseDouble(st2.nextToken());
                    double lon = Double.parseDouble(st2.nextToken());
                    double radius = Double.parseDouble(st.nextToken());
                    double deltaLat = radius / 111.2;
                    double deltaLon = radius / (111.2 * Math.cos(Math.toRadians(lat)));
                    if (lat + deltaLat > latMax) {
                        latMax = lat + deltaLat;
                    }
                    if (lat - deltaLat < latMin) {
                        latMin = lat - deltaLat;
                    }
                    if (lon + deltaLon > lonMax) {
                        lonMax = lon + deltaLon;
                    }
                    if (lon - deltaLon < lonMin) {
                        lonMin = lon - deltaLon;
                    }
                } catch (Exception e) {
                }
            }
        }
        if (countPolygons() > 0) {
            resetPolygons();
            while (hasNextPolygon()) {
                try {
                    StringTokenizer st = new StringTokenizer(getNextPolygon());
                    while (st.hasMoreTokens()) {
                        StringTokenizer st2 = new StringTokenizer(st.nextToken(), ",");
                        double lat = Double.parseDouble(st2.nextToken());
                        double lon = Double.parseDouble(st2.nextToken());
                        if (lat > latMax) {
                            latMax = lat;
                        }
                        if (lat < latMin) {
                            latMin = lat;
                        }
                        if (lon > lonMax) {
                            lonMax = lon;
                        }
                        if (lon < lonMin) {
                            lonMin = lon;
                        }
                    }
                } catch (Exception e) {
                }
            }
        }
        bounds = Double.toString(latMax) + "," + Double.toString(lonMin) + " " + Double.toString(latMin) + "," + Double.toString(lonMax);
        return bounds;
    }

    /** Return raw EAS-CAP Area fragment */
    public String toEASCAP() {
        buf = new StringBuffer();

        if (areaDesc != null) {
            buf.append("<areaDesc>" + EasCapAlert.entities(areaDesc) + "</areaDesc>\n");
        }

        resetPolygons();
        while (hasNextPolygon()) {
            buf.append("<polygon>" + EasCapAlert.entities(getNextPolygon()) + "</polygon>\n");
        }

        resetCircles();
        while (hasNextCircle()) {
            buf.append("<circle>" + EasCapAlert.entities(getNextCircle()) + "</circle>\n");
        }

        resetGeocodes();
        while (hasNextGeocode()) {
            buf.append("<geocode>\n" + EasCapAlert.indent(getNextGeocodeValuePair().toString()) + "</geocode>\n");
        }

        if (altitude != null) {
            buf.append("<altitude>" + EasCapAlert.entities(altitude) + "</altitude>\n");
        }

        if (ceiling != null) {
            buf.append("<ceiling>" + EasCapAlert.entities(ceiling) + "</ceiling>\n");
        }

        return buf.toString();
    }

    /** Overridden toString() to return XML fragment */
    public String toString() {
        buf = new StringBuffer();
        if (areaDesc != null) {
            buf.append("<areaDesc>" + EasCapAlert.entities(areaDesc) + "</areaDesc>\n");
        } else {
            buf.append("<areaDesc>" + EasCapAlert.entities("Undefined area") + "</areaDesc>\n");
        }

        resetPolygons();
        while (hasNextPolygon()) {
            buf.append("<polygon>" + EasCapAlert.entities(getNextPolygon()) + "</polygon>\n");
        }
        resetCircles();
        while (hasNextCircle()) {
            buf.append("<circle>" + EasCapAlert.entities(getNextCircle()) + "</circle>\n");
        }
        resetGeocodes();
        while (hasNextGeocode()) {
            buf.append("<geocode>\n" + EasCapAlert.indent(getNextGeocodeValuePair().toString()) + "</geocode>\n");
        }
        if (altitude != null) {
            buf.append("<altitude>" + EasCapAlert.entities(altitude) + "</altitude>\n");
        }
        if (ceiling != null) {
            buf.append("<ceiling>" + EasCapAlert.entities(ceiling) + "</ceiling>\n");
        }
        return buf.toString();
    }

    /** Return CAP 1.0 fragment */
    public String toCAP10() {
        buf = new StringBuffer();
        if (areaDesc != null) {
            buf.append("<areaDesc>" + EasCapAlert.entities(areaDesc) + "</areaDesc>\n");
        } else {
            buf.append("<areaDesc>" + EasCapAlert.entities("Undefined area") + "</areaDesc>\n");
        }
        resetPolygons();
        while (hasNextPolygon()) {
            buf.append("<polygon>" + EasCapAlert.entities(getNextPolygon()) + "</polygon>\n");
        }
        resetCircles();
        while (hasNextCircle()) {
            buf.append("<circle>" + EasCapAlert.entities(getNextCircle()) + "</circle>\n");
        }
        resetGeocodes();
        while (hasNextGeocode()) {
            buf.append("<geocode>" + EasCapAlert.entities(getNextGeocodeValuePair().toCAP10()) + "</geocode>\n");
        }
        if (altitude != null) {
            buf.append("<altitude>" + EasCapAlert.entities(altitude) + "</altitude>\n");
        }
        if (ceiling != null) {
            buf.append("<ceiling>" + EasCapAlert.entities(ceiling) + "</ceiling>\n");
        }
        return buf.toString();
    }
}
